﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace YTCMS.Core
{
    public class ReflectionHelper
    {
        public static CultureInfo CULTURE_INFO = new CultureInfo("zh-CN", false);

        /// <summary>
        /// 检查传入对象是否为空
        /// </summary>
        /// <param name="o">The object to test.</param>
        /// <returns>True if null was passed as argument.</returns>
        public static bool IsNull(object o)
        {
            return (null == o);
        }

        public static TypeCode GetTypeCode(object o)
        {
            return (null == o) ? TypeCode.Empty : Type.GetTypeCode(o.GetType());
        }

        /// <summary>
        /// 检查传入的对象是否属于集合对象
        /// </summary>
        /// <param name="o"></param>
        /// <returns></returns>
        public static bool IsEnumerable(object o)
        {
            return (null == o) ? false : o.GetType().GetInterface("System.Collections.IEnumerable") != null ? true : false;
        }

        /// <summary>
        /// Casts a certain valueObject into a certain type, by using ChangeType when possible
        /// (i.e. except for exceptions).
        /// </summary>
        /// <param name="valueObject">The object to cast</param>
        /// <param name="typeName">The full name of the type to cast to</param>
        /// <returns>A properly casted object, unless an exception occurs.</returns>
        public static object CastValue(object valueObject, string typeName)
        {
            return CastValue(valueObject, Type.GetType(typeName));
        }

        /// <summary>
        /// Casts a certain valueObject into a certain type, by using ChangeType when possible
        /// </summary>
        /// <param name="valueObject">The object to cast</param>
        /// <param name="type">The type to cast to</param>
        /// <returns>A properly casted object, unless an exception occurs.</returns>
        public static object CastValue(object valueObject, Type type)
        {
            if (type == typeof(System.Exception))
            {
                return new System.Exception((string)valueObject);
            }
            // fix to bug 1474032 provided by Brian Matthews
            else if (type.IsEnum)
            {
                return Enum.Parse(type, valueObject.ToString(), false);
            }
            else
            {
                return Convert.ChangeType(valueObject, type, CULTURE_INFO);
            }
        }

        /// <summary>
        /// Calls a member on an object, either to get or set an attribute or property,
        /// or to invoke a method.
        /// </summary>
        /// <param name="target">The object on which the member will be called.</param>
        /// <param name="name">The name of the member to call.</param>
        /// <param name="argValues">An array of arguments. Use null if no argument is needed.</param>
        /// <returns>The value returned when the member is a method, else null.</returns>
        public static object ObjectCall(object target, string name, object[] argValues)
        {
            return Call(target.GetType(), target, name, argValues);
        }

        /// <summary>
        /// Calls a static member on a class, either to get or set an attribute or property,
        /// or to invoke a method.
        /// </summary>
        /// <param name="type">The fully qualified name of the class to call.</param>
        /// <param name="name">The name of the member to call.</param>
        /// <param name="argValues">An array of arguments. Use null if no argument is needed.</param>
        /// <returns>The value returned when the member is a method, else null.</returns>
        public static object ClassCall(string type, string name, object[] argValues)
        {
            return Call(GetRuntimeType(type), null, name, argValues);
        }

        /// <summary>
        /// Instantiates a new object.
        /// </summary>
        /// <param name="type">The fully qualified name of the class to instantiate.</param>
        /// <param name="argValues">An array of contructor arguments. Use null if no argument is needed.</param>
        /// <returns></returns>
        public static object ClassNew(string type, object[] argValues)
        {
            if (argValues == null) argValues = new object[0];
            Type toInstantiate = GetRuntimeType(type);
            ConstructorInfo ci = toInstantiate.GetConstructor(GetArgumentTypes(argValues));
            if (ci == null)
            {
                throw new TargetException("No matching constructor found on " + type);
            }
            return ci.Invoke(argValues);
        }

        public static object ClassNew(string assembly, string type, object[] argValues)
        {
            if (argValues == null) argValues = new object[0];
            Type toInstantiate = GetRuntimeType(assembly, type);
            ConstructorInfo ci = toInstantiate.GetConstructor(GetArgumentTypes(argValues));
            if (ci == null)
            {
                throw new TargetException("No matching constructor found on " + type);
            }
            return ci.Invoke(argValues);
        }

        // Private methods ------------------------------------------------------------------
        private static object Call(Type type, object target, string name, object[] argValues)
        {
            int nbOfProvidedArgs = 0;
            if (argValues != null)
                nbOfProvidedArgs = argValues.Length;
            // Check if it is a field name
            FieldInfo fi = type.GetField(name);
            if (fi != null)
            {
                if (nbOfProvidedArgs > 0)
                {
                    if (nbOfProvidedArgs == 1)
                    {
                        fi.SetValue(target, argValues[0]);
                        return null;
                    }
                    else
                    {
                        throw new TargetException(nbOfProvidedArgs + " argument(s) provided for field " + target + "." + name + " when 1 expected");
                    }
                }
                else
                {
                    return fi.GetValue(target);
                }
            }

            // Check if it is a property name
            MethodInfo mi = null;
            PropertyInfo pi;
            if (nbOfProvidedArgs > 0)
            {
                // If we have arguments passed, try to locate a property mathing those
                pi = type.GetProperty(name, GetArgumentTypes(argValues));
                if (pi != null)
                {
                    // If the property is found, give priority to getter
                    // This is of course a limitation, for very specific things 
                    // NxBRE users will enjoy implementing code delegates!
                    mi = pi.GetGetMethod();
                    if (mi == null) mi = pi.GetSetMethod();
                }
                else
                {
                    // If the property is found, try to find a basic non-argument property
                    // and target the set method as we have arguments passed
                    pi = type.GetProperty(name);
                    if (pi != null) mi = pi.GetSetMethod();
                }
            }
            else
            {
                // Having no arguments passed, we clearly want a getter.
                pi = type.GetProperty(name);
                if (pi != null) mi = pi.GetGetMethod();
            }

            // check if it is a method name
            Type[] types = GetArgumentTypes(argValues);
            if (mi == null) mi = type.GetMethod(name, types);
            // the last option is to try to find if there is a single method with the desired name
            if (mi == null)
            {
                try
                {
                    mi = type.GetMethod(name);
                }
                catch (AmbiguousMatchException)
                {
                    // IGNORED
                }
            }

            // if something has been found
            if (mi != null)
            {
                if (argValues == null && mi.GetParameters().Length == 0)
                    return mi.Invoke(target, argValues);
                // if the number of arguments match, perform the invocation
                else if (argValues.Length == mi.GetParameters().Length)
                    return mi.Invoke(target, argValues);
            }

            // nothing found
            return CallMethodWithByRefParametersOrThrow(type,
                                                        target,
                                                        name,
                                                        argValues,
                                                        new TargetException("Can not find member " + type.FullName
                                                                              + "." + name
                                                                              + CollectionHelper.ArrayToString(types)
                                                                              + " for values "
                                                                              + CollectionHelper.ArrayToString(argValues)));
        }

        private static object CallMethodWithByRefParametersOrThrow(Type type, object target, string methodName, object[] args, TargetException te)
        {
            MethodInfo[] mis = type.GetMethods();
            foreach (MethodInfo mi in mis)
            {
                ParameterInfo[] parameters = mi.GetParameters();
                if ((mi.Name.Equals(methodName)) && (parameters.Length == args.Length))
                {
                    bool allMatch = true;
                    IList<int> byRefArgIndices = new List<int>();
                    for (int i = 0; i < parameters.Length; i++)
                    {
                        Type rawParameterType = parameters[i].ParameterType;
                        Type parameterType = rawParameterType.IsByRef ? rawParameterType.GetElementType() : rawParameterType;
                        if (!(parameterType.IsAssignableFrom(args[i].GetType())))
                        {
                            allMatch = false;
                            break;
                        }
                        else if (rawParameterType.IsByRef)
                        {
                            byRefArgIndices.Add(i);
                        }
                    }
                    if (allMatch)
                    {
                        object invocationResult = mi.Invoke(target, args);
                        if (byRefArgIndices.Count == 0)
                        {
                            return invocationResult;
                        }
                        else
                        {
                            object[] result = new object[1 + byRefArgIndices.Count];
                            result[0] = invocationResult;
                            for (int i = 0; i < byRefArgIndices.Count; i++)
                            {
                                result[1 + i] = args[byRefArgIndices[i]];
                            }
                            return result;
                        }
                    }
                }
            }
            throw te;
        }

        private static Assembly GetAssembly(string assemblyName)
        {
            System.Reflection.Assembly assembly = null;
            foreach (System.Reflection.Assembly ase in AppDomain.CurrentDomain.GetAssemblies())
            {
                if (ase.FullName.Trim().Equals(assemblyName.Trim()))
                    assembly = ase;
            }
            return assembly;
        }

        private static Type GetRuntimeType(string type)
        {
            Type runtimeType = Type.GetType(type, true);
            if (runtimeType == null)
            {
                throw new TargetException("Can not find class type " + type);
            }
            else
            {
                return runtimeType;
            }
        }

        public static Type GetRuntimeType(string assemblyName, string type)
        {
            Assembly assembly = GetAssembly(assemblyName);
            if (assembly == null)
                throw new TargetException("Can not find class type " + type);
            Type runtimeType = assembly.GetType(type, true);
            if (runtimeType == null)
            {
                throw new TargetException("Can not find class type " + type);
            }
            else
            {
                return runtimeType;
            }
        }

        private static Type[] GetArgumentTypes(object[] arguments)
        {
            ArrayList argumentTypes = new ArrayList();
            if (arguments != null)
                for (int i = 0; i < arguments.Length; i++)
                {
                    if (arguments[i] != null)
                    {
                        argumentTypes.Add(arguments[i].GetType());
                    }
                    else
                    {
                        argumentTypes.Add(typeof(Object));
                    }
                }
            return (Type[])argumentTypes.ToArray(typeof(Type));
        }
    }//End class
}
